Tubes.Permissions = Tubes.Permissions or {}
local Permissions = Tubes.Permissions
local ShouldSuppressNetwork = false

Permissions.PlayerGroups = {}
Permissions.Groups = {}
Permissions.StringPool = Tubes.StringPool (65536)
Tubes.EventProvider (Permissions)

function Permissions.CreateGroup (groupName, predicate)
	local Group = Permissions.Groups [groupName]
	if Group then
		return Group
	end
	Group = Tubes.PermissionGroup (groupName, predicate)
	Group:SetOwner ("Server")
	Permissions.PlayerGroups ["Server"] = Permissions.PlayerGroups ["Server"] or {}
	Permissions.PlayerGroups ["Server"] [groupName] = Group
	Permissions.Groups [groupName] = Group
	
	Permissions.StringPool:AddString (groupName)
	
	Permissions.DispatchEvent ("GroupCreated", nil, groupName, Group)
	
	if not ShouldSuppressNetwork then
		Tubes.Networking.Send ("GroupCreated", nil, nil, groupName)
	end
	
	return Group
end

function Permissions.CreatePlayerGroup (ply, groupName, predicate)
	if type (ply) == "string" then
		for _, v in ipairs (player.GetAll ()) do
			if v:SteamID () == ply then
				ply = v
				break
			end
		end
	end
	local SteamID = Tubes.GetPlayerSteamID (ply)
	local FullGroupName = SteamID .. "/" .. groupName
	local Group = Permissions.Groups [FullGroupName]
	if Group then
		return Group
	end
	Group = Tubes.PermissionGroup (groupName, predicate)
	Group:SetOwner (ply)
	Group:SetFullName (FullGroupName)
	Permissions.PlayerGroups [ply] = Permissions.PlayerGroups [ply] or {}
	Permissions.PlayerGroups [ply] [groupName] = Group
	Permissions.Groups [FullGroupName] = Group
	
	Permissions.StringPool:AddString (groupName)
	Permissions.StringPool:AddString (FullGroupName)
	
	Permissions.DispatchEvent ("GroupCreated", SteamID, groupName, Group)
	
	if not ShouldSuppressNetwork then
		Tubes.Networking.Send ("GroupCreated", nil, SteamID, groupName)
	end
	
	return Group
end

function Permissions.GetGroup (groupName)
	return Permissions.Groups [groupName]
end

function Permissions.GetGroupIterator ()
	local Next, Table, Key = pairs (Permissions.Groups)
	return function ()
		Key = Next (Table, Key)
		return Key, Table [Key]
	end
end

function Permissions.GetPlayerGroupIterator (ply)
	ply = ply or "Server"
	if not Permissions.PlayerGroups [ply] then
		return function () end
	end
	local Next, Table, Key = pairs (Permissions.PlayerGroups [ply])
	return function ()
		Key = Next (Table, Key)
		return Key, Table [Key]
	end
end

function Permissions.GetPlayerGroups ()
	return Permissions.PlayerGroups
end

function Permissions.SuppressNetwork (suppress)
	Permissions.ShouldSuppressNetwork = suppress
end

-- Syncing
Tubes.Networking.RegisterReader ("GroupCreated", function (netMessage)
	Permissions.SuppressNetwork (true)
	local GroupName = netMessage:String ()
	local SteamID = netMessage:String ()
	if SERVER then
		if SteamID ~= netMessage:GetSource ():SteamID () then
			Permissions.SuppressNetwork (false)
			return
		end
	end
	if SteamID == "" then
		Permissions.CreateGroup (GroupName)
	else
		Permissions.CreatePlayerGroup (SteamID, GroupName)
	end
	Permissions.SuppressNetwork (false)
end)

Tubes.Networking.RegisterSender ("GroupCreated", function (netMessage, steamID, groupName)
	netMessage:String (groupName)
	netMessage:String (steamID or "")
end)

Tubes.Networking.RegisterReader ("GroupMembersAdded", function (netMessage)
	local GroupName = Permissions.StringPool:LookupString (netMessage:UShort ())
	local MemberCount = netMessage:UChar ()
	local Group = Permissions.GetGroup (GroupName)
	if SERVER and netMessage:GetSource () ~= Group:GetOwner () then
		return
	end
	for i = 1, MemberCount do
		Group:AddPlayer (netMessage:String ())
	end
end)

Tubes.Networking.RegisterSender ("GroupMembersAdded", function (netMessage, group, players)
	netMessage:UShort (Permissions.StringPool:ComputeIndex (group:GetFullName ()))
	if players then
		netMessage:UChar (#players)
		for i = 1, #players do
			netMessage:String (Tubes.GetPlayerSteamID (players [i]))
		end
	else
		netMessage:UChar (group:GetCachedMemberCount ())
		for steamID in group:GetCachedMemberIterator () do
			netMessage:String (steamID)
		end
	end
end)

Tubes.Networking.RegisterSender ("GroupMembersRemoved", function (netMessage, group, players)
	netMessage:UShort (Permissions.StringPool:ComputeIndex (group:GetFullName ()))
	netMessage:UChar (group:GetCachedMemberCount ())
	for steamID in group:GetCachedMemberIterator () do
		netMessage:String (steamID)
	end
end)

-- Events
Tubes:AddEventListener ("PlayerConnected", function (player, isLocalPlayer)
	Permissions.CreatePlayerGroup (player, "Player", function (_, ply)
		return ply == player
	end):SetSingular (true)
	
	Permissions.CreatePlayerGroup (player, "Friends", isLocalPlayer and function (_, ply)
		return ply:GetFriendStatus () == "friend"
	end)
	
	local Local = SERVER and "Server" or LocalPlayer ()
	for _, Group in Permissions.GetPlayerGroupIterator (Local) do
		if Group:IsInGroup (nil, player) then
			Group:AddPlayer (player)
		end
	end
end)

Tubes:AddEventListener ("PlayerDisconnected", function (ply)
	Permissions [Tubes.GetPlayerSteamID (ply)] = Permissions [ply]
	Permissions [ply] = nil
	
	local Local = SERVER and "Server" or LocalPlayer ()
	for _, Group in Permissions.GetPlayerGroupIterator (Local) do
		if Group:IsInMemberCache (player) then
			Group:RemovePlayer (player)
		end
	end
end)

-- Inbuilt groups
Permissions.CreateGroup ("Owner", function (permissionBlock, ply)
	if not permissionBlock then
		return false
	end
	return permissionBlock:GetOwner () == ply
end):SetSingular (true)

Permissions.CreateGroup ("Everyone", function (_, ply)
	return true
end)

Permissions.CreateGroup ("Administrators", function (_, ply)
	return ply and ply:IsAdmin () or false
end)

Permissions.CreateGroup ("Super Administrators", function (_, ply)
	return ply and ply:IsSuperAdmin () or false
end)